use crate::{
    db::models::{Contract, Payment},
    schema::{
        contract::{self},
        payment::{self, dsl::*},
        project,
        provider::{self},
    },
};
use actix_web::{
    post,
    web::{self, Data, Json},
    HttpResponse,
};
use diesel::{
    dsl::sql,
    insert_into,
    sql_types::{BigInt, Integer},
    update,
};
use diesel::{prelude::*, sql_types::Bool};

use crate::{api::Page, DbPool};

#[derive(Debug, serde::Deserialize)]
pub struct PagePaymentReq {
    page: i64,
    size: i64,
    project_id: i32,
    provider_id: i32,
    contract_id: i32,
    start_date: i64,
    end_date: i64,
}

#[derive(Debug, serde::Serialize, Queryable)]
pub struct PaymentVo {
    id: i32,
    key: i32,
    project_id: i32,
    project_name: String,
    provider_id: i32,
    provider_name: String,
    contract_id: i32,
    contract_no: String,
    pay_amount: f64,
    pay_date: i64,
    payer: String,
    special: Vec<u8>,
    special_payment: Option<String>,
    special_amount: Option<f64>,
    remarks: Option<String>,
}

const SIZE_MAX: i64 = 50000000000;
const SIZE_INVERSE: i64 = -1;

#[post("/page")]
async fn page_payment(
    db: Data<DbPool>,
    request: web::Json<PagePaymentReq>,
) -> Json<Page<PaymentVo>> {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();
        let mut size = req.size;
        if req.size.eq(&SIZE_INVERSE) {
            size = SIZE_MAX;
        }
        let data = payment::table
            .inner_join(
                contract::table
                    .inner_join(provider::table)
                    .inner_join(project::table),
            )
            .filter(
                sql::<Bool>("(payment.contract_id = ")
                    .bind::<Integer, _>(req.contract_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.contract_id)
                    .sql(" ) and ( project_id = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(") and ( provider_id = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(") and pay_date >= ")
                    .bind::<BigInt, _>(req.start_date)
                    .sql(" and pay_date <= ")
                    .bind::<BigInt, _>(req.end_date)
                    .sql(" order by pay_date desc"),
            )
            .limit(size)
            .offset((req.page - 1) * size)
            .select((
                payment::id,
                payment::id,
                project::id,
                project::name,
                provider::id,
                provider::name,
                contract::id,
                contract::no,
                payment::pay_amount,
                payment::pay_date,
                payment::payer,
                payment::special,
                payment::special_payment,
                payment::special_amount,
                payment::remarks,
            ))
            .load::<PaymentVo>(&mut conn);
        let c: Result<i64, _> = payment::table
            .inner_join(
                contract::table
                    .inner_join(provider::table)
                    .inner_join(project::table),
            )
            .filter(
                sql::<Bool>("(payment.contract_id = ")
                    .bind::<Integer, _>(req.contract_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.contract_id)
                    .sql(" ) and ( project_id = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(") and ( provider_id = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(") and pay_date >= ")
                    .bind::<BigInt, _>(req.start_date)
                    .sql(" and pay_date <= ")
                    .bind::<BigInt, _>(req.end_date)
                    .sql(" order by pay_date desc"),
            )
            .select(diesel::dsl::count(payment::id))
            .first(&mut conn);
        match data {
            Ok(d) => Page {
                total: c.unwrap(),
                content: d,
            },
            Err(e) => {
                println!("{:?}", e);
                return Page {
                    total: 0,
                    content: vec![],
                };
            }
        }
    })
    .await;
    Json(data.unwrap())
}

#[derive(Debug, serde::Deserialize)]
pub struct CreatePaymentVo {
    contract_no: i32,
    pay_date: i64,
    payer: String,
    special_payment: Option<String>,
    special_amount: Option<f64>,
    pay_amount: Option<f64>,
    remarks: Option<String>,
}

#[post("/create")]
async fn create_payment(db: Data<DbPool>, request: web::Json<CreatePaymentVo>) -> HttpResponse {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();
        // 判断合同是否存在
        let the_contract: Result<Contract, _> = contract::dsl::contract
            .filter(contract::id.eq(&req.contract_no))
            .first::<Contract>(&mut conn);
        if let Err(_) = the_contract {
            return 1;
        }
        let the_contract = the_contract.unwrap();

        let mut is_special_payment: Vec<u8> = vec![0];
        let mut the_payment_amount: f64 = 0.0;
        if let Some(pa) = req.pay_amount {
            the_payment_amount = pa;
        } else if let Some(sa) = req.special_amount {
            the_payment_amount = sa;
            is_special_payment = vec![1];
        }

        let payment_to_save = Payment {
            id: 0,
            contract_id: the_contract.id,
            pay_amount: the_payment_amount,
            pay_date: req.pay_date,
            payer: req.payer.clone(),
            special: is_special_payment,
            special_payment: req.special_payment,
            special_amount: req.special_amount,
            remarks: req.remarks,
        };
        let result = insert_into(payment)
            .values(&payment_to_save)
            .execute(&mut conn);

        match result {
            Ok(_) => 200,
            _ => 0,
        }
    })
    .await;
    match data.unwrap() {
        0 => HttpResponse::BadRequest().body("合同已存在"),
        1 => HttpResponse::BadRequest().body("项目不存在"),
        2 => HttpResponse::BadRequest().body("供应商不存在"),
        3 => HttpResponse::BadRequest().body("主合同不存在"),
        _ => HttpResponse::Ok().json(true),
    }
}

#[derive(Debug, serde::Deserialize)]
pub struct UpdatePaymentVo {
    id: i32,
    contract_no: i32,
    pay_date: i64,
    payer: String,
    special_payment: Option<String>,
    special_amount: Option<f64>,
    pay_amount: Option<f64>,
    remarks: Option<String>,
}

#[post("/update")]
async fn update_payment(db: Data<DbPool>, request: web::Json<UpdatePaymentVo>) -> HttpResponse {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();

        // 判断合同是否存在
        let the_contract: Result<Contract, _> = contract::dsl::contract
            .filter(contract::id.eq(&req.contract_no))
            .first::<Contract>(&mut conn);
        if let Err(_) = the_contract {
            return 1;
        }

        let the_contract = the_contract.unwrap();

        let mut is_special_payment: Vec<u8> = vec![0];
        let mut the_payment_amount: f64 = 0.0;
        if let Some(pa) = req.pay_amount {
            the_payment_amount = pa;
        } else if let Some(sa) = req.special_amount {
            the_payment_amount = sa;
            is_special_payment = vec![1];
        }

        let result: Result<usize, _> = update(payment.filter(payment::id.eq(req.id)))
            .set((
                payment::contract_id.eq(the_contract.id),
                payment::pay_amount.eq(the_payment_amount),
                payment::pay_date.eq(req.pay_date),
                payment::payer.eq(req.payer),
                payment::special.eq(is_special_payment),
                payment::special_payment.eq(req.special_payment),
                payment::special_amount.eq(req.special_amount),
                payment::remarks.eq(req.remarks),
            ))
            .execute(&mut conn);
        match result {
            Ok(_) => 200,
            _ => 0,
        }
    })
    .await;
    match data.unwrap() {
        0 => HttpResponse::BadRequest().body("合同已存在"),
        1 => HttpResponse::BadRequest().body("项目不存在"),
        2 => HttpResponse::BadRequest().body("供应商不存在"),
        3 => HttpResponse::BadRequest().body("主合同不存在"),
        _ => HttpResponse::Ok().json(true),
    }
}

#[derive(Debug, serde::Deserialize)]
pub struct DeletePaymentReq {
    id: i32,
}

#[post("/delete")]
async fn delete_payment(db: Data<DbPool>, request: web::Json<DeletePaymentReq>) -> HttpResponse {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();
        let result = diesel::delete(payment.filter(payment::id.eq(req.id))).execute(&mut conn);
        match result {
            Ok(d) => d,
            _ => 0,
        }
    })
    .await;
    match data.unwrap() {
        0 => HttpResponse::BadRequest().body("未找到记录"),
        _ => HttpResponse::Ok().json(true),
    }
}
